package com.gz.spring.ioc;

import com.gz.spring.annotation.Autowired;
import com.gz.spring.annotation.Component;
import com.gz.spring.annotation.ComponentScan;
import com.gz.spring.annotation.Scope;
import com.gz.spring.processor.BeanPostProcessor;
import com.gz.spring.processor.InitializingBean;
import org.apache.commons.lang.StringUtils;


import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

public class GzSpringApplicationContext {
    private Class configClass;

    //定义属性BeanDefinitionMao ->存在BeanDefinition对象
    private ConcurrentHashMap<String,BeanDefinition> beanDefinitionMap=
            new ConcurrentHashMap<>();
    //定义属性SingletonObjects ->存放单例对象
    private ConcurrentHashMap<String,Object> singletonObjects   =
            new ConcurrentHashMap<>();

    //定义一个ArrayList,存放后置处理器
    private List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();

    public GzSpringApplicationContext(Class configClass) {

        beanDefinitionScan(configClass);

        //通过beanDefinitionMap初始化singletonObjects单例池
        Enumeration<String> keys = beanDefinitionMap.keys(); //beanName
        while (keys.hasMoreElements()) {
            String beanName = keys.nextElement();
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            if("singleton".equalsIgnoreCase(beanDefinition.getScope())) {
                Object bean = createBean(beanName,beanDefinition);
                singletonObjects.put(beanName,bean);
            }
        }

        System.out.println("singletonObjects单例池= "+singletonObjects);
        System.out.println("beanDefinitionMap= "+beanDefinitionMap);
    }


    private Object createBean(String beanName,BeanDefinition beanDefinition) {
        //得到Bean的实例对象
        Class clazz = beanDefinition.getClazz();

        //使用反射得到实例
        try {
            Object o = clazz.getDeclaredConstructor().newInstance();

            //加入业务逻辑

            //1.遍历当前要创建的对象的所有字段
            //如private MonsterDao monsterDao;
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                //2.判断该字段是否有@Autowired修饰
                if (field.isAnnotationPresent(Autowired.class)) {



                    //3.得到字段名
                    String name = field.getName();//monsterDao
//                    System.out.println("字段名= "+name);
                    //4.根据字段名获取Bean对象
                    Object bean = getBean(name);
                    //5.进行组装
                    //因为private MonsterDao monsterDao;是私有属性
                    //所以需要爆破才能设值
                    field.setAccessible(true);
                    field.set(o,bean);
                }
            }
            System.out.println("=======实例被创建======="+o);
            //在Bean的初始化方法前，调用后置处理器before方法
            //这里可以修改
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                Object current = beanPostProcessor.postProcessBeforeInitialization(o, beanName);
                if (current!=null){ //这样返回为空的话，就不做处理
                    o=current;
                }
            }

            //判断当前创建的Bean对象是否实现了InitializingBean
            if(o instanceof InitializingBean){
                try {
                    ((InitializingBean)o).afterPropertiesSet();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            //在Bean的初始化方法后，调用后置处理器after方法
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                Object current= beanPostProcessor.postProcessAfterInitialization(o, beanName);
                if (current!=null){
                    o=current;
                }
            }

            return o;
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return null;
    }

    //返回容器中对象
    public Object getBean(String beanName) {
        if(beanDefinitionMap.containsKey(beanName)) {
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            if("singleton".equalsIgnoreCase(beanDefinition.getScope())) {
                //如果beanDefinition存在并且该对象是单例的
                return singletonObjects.get(beanName);
            }else {//如果不是单例的
                return createBean(beanName,beanDefinition);
            }
        }else {
            throw new NullPointerException("没有该bean");
        }
    }

    //该方法完成对指定包的扫描，并将Bean信息封装到BeanDefinition对象，并放入到Map中
    public void beanDefinitionScan(Class configClass){
        this.configClass = configClass;
        //获得配置文件(这里是GzSpringConfig的注解)
        ComponentScan componentScan= (ComponentScan)this.configClass.
                getDeclaredAnnotation(ComponentScan.class);
        String path = componentScan.value();
//        System.out.println("要扫描的包="+path); //com.gz.spring.component

        //得到类加载器
        ClassLoader classLoader = GzSpringApplicationContext.class.getClassLoader();
        //通过类加载器获取到要扫描的包的资源 url
        //注意这里不直接用path是因为path是com.gz.spring.component,
        //而getResource需要的是     com/gz/spring/component
        path= path.replace('.', '/');
        URL resource = classLoader.getResource(path);
        //resource=/E:/Java/java_code/spring/out/production/spring/com/gz/spring/component
//        System.out.println("resource="+resource);

        //将要加载的资源(.class)路径下的文件进行遍历(io)
        File file = new File(resource.getFile());
        if(file.isDirectory()) {
            File[] files = file.listFiles();
            for (File f : files) {
//                System.out.println(f.getAbsolutePath());
                //E:\Java\java_code\spring\out\production\spring\com\gz\spring\component\MyComponent.class
                String absolutePath = f.getAbsolutePath();

                //这里过滤一下，只要.class结尾的
                if(absolutePath.endsWith(".class")) {
                    //1.获取到类名
                    //substring是左闭右开[)
                    //得到类名MyComponent
                    String className = absolutePath.substring(absolutePath.lastIndexOf("\\") + 1
                            , absolutePath.indexOf(".class"));
//                    System.out.println(className);
                    //2.得到com.gz.spring.component.MyComponent
                    String classFullName=path.replace("/",".")+"."+className;
//                    System.out.println(classFullName);

                    //3.判断该类是不是需要注入容器，因为有的类上面没有写@Controller、@Repository等注解
                    //这里用Class.forName(classFullName)也行
                    //两者区别:
                    //(1)Class.forName会调用静态方法
                    //(2)classLoader.loadClass不会，可以理解为轻量级的
                    try {
                        //Class<?> aClass = Class.forName(classFullName);
                        Class<?> clazz = classLoader.loadClass(classFullName);
                        if(clazz.isAnnotationPresent(Component.class) ) {
                            //如果使用了Component注解，说明是Spring bean
//                            System.out.println("是一个Spring bean = "+clazz+" 类名="+className);

                            //为了方便，这里将后置处理器放入到一个ArrayList中
                            //如果是一个后置处理器，放入到beanPostProcessorList中
                            //在原生的Spring容器中，对后置处理器还是走的getBean,createBean
                            //但是需要在singletonObjects中加入更多逻辑，这里简化了

                            //判断当前clazz是否实现了BeanPostProcessor，不能用instanceof
                            //因为这里不是实例化对象，而是一个class类对象
                            if(BeanPostProcessor.class.isAssignableFrom(clazz)) {
                                BeanPostProcessor beanPostProcessor=
                                        (BeanPostProcessor)clazz.newInstance();
                                beanPostProcessorList.add(beanPostProcessor);
                                //如果实现了BeanPostProcessor，那么就是后置处理器
                                //不用再继续当做Bean类放入单例池，否则后置处理器方法会出现两次
                                continue;
                            }


                            //得到beanName
                            String id = clazz.getDeclaredAnnotation(Component.class).value();
                            if (!"".equals(id)) {
                                className=id;
                            }
                            BeanDefinition beanDefinition = new BeanDefinition();
                            beanDefinition.setClazz(clazz);
                            //获取scope
                            if(clazz.isAnnotationPresent(Scope.class)) { //如果配置了scope
                                String scope = clazz.getDeclaredAnnotation(Scope.class).value();
                                if ("singleton".equals(scope)||"prototype".equals(scope)) {
                                    beanDefinition.setScope(scope);
                                }
                            }else { //如果没有配置scope,默认singleton
                                beanDefinition.setScope("singleton");
                            }
                            className= StringUtils.uncapitalize(className);
                            //将BeanDefinition放入BeanDefinitionMap
                            beanDefinitionMap.put(className, beanDefinition);

                        }else {
                            //如果没有使用Component注解，说明不是Spring bean
                            System.out.println("不是一个Spring bean = "+clazz+" 类名="+className);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}
